import 'package:dio/dio.dart';
import 'package:flutter_enjoy_plus/utils/Toast.dart';
import 'package:flutter_enjoy_plus/utils/eventBus.dart';

import '../constants/index.dart';
import 'TokenManager.dart';

class RequestDio {
  // 1.创建dio 实例
  final Dio _dio = Dio();
  // 2.定义构造函数
  RequestDio() {
    // 3.配置基地址和超时时间
    // _dio.options.baseUrl = GlobalVariable.BASE_URL;
    // _dio.options.connectTimeout =
    //     const Duration(seconds: GlobalVariable.TIMEOUT);
    // _dio.options.receiveTimeout =
    //     const Duration(seconds: GlobalVariable.TIMEOUT);
    // 针对一个对象连续的进行赋值可以用 .. 的方式
    _dio
      ..options.baseUrl = GlobalVariable.BASE_URL
      ..options.connectTimeout = const Duration(seconds: GlobalVariable.TIMEOUT)
      ..options.receiveTimeout =
          const Duration(seconds: GlobalVariable.TIMEOUT);
    // 4.添加请求和响应拦截器
    _registerInterceptors();
  }
  // 5.封装请求方法
  // get 方法
  Future get(String url, {Map<String, dynamic>? params}) async {
    final result = await _dio.get(url, queryParameters: params);
    return _handelResult(result);
  }

  // post 方法
  Future post(String url, {Map<String, dynamic>? data}) async {
    final result = await _dio.post(url, data: data);
    return _handelResult(result);
  }

  // update 方法 // 接口需要formData类型数据，之前写的方法无法满足
  Future update(String url, {FormData? data}) async {
    final result = await _dio.post(url, data: data);
    return _handelResult(result);
  }

  // put 方法
  Future put(String url, {Map<String, dynamic>? data}) async {
    final result = await _dio.put(url, data: data);
    return _handelResult(result);
  }

  // delete 方法
  Future delete(String url, {Map<String, dynamic>? params}) async {
    final result = await _dio.delete(url, queryParameters: params);
    return _handelResult(result);
  }

  // 统一处理返回的结果
  _handelResult(Response<dynamic> result) {
    if (result.data['code'] == GlobalVariable.SUCCESS_CODE) {
      // 成功返回数据
      return result.data['data'];
    } else {
      // 错误提示 用户看
      PrompAction.showError(result.data['message']);
      // 返回错误 我们程序员看
      throw Exception(result.data['message']);
    }
  }

  Future<bool> _isRefreshsTokenExpired() async {
    // 1. 获取刷新token
    // 2. 发请求 -> /refreshToken
    // 3. 该请求中传入refreshToken
    // 4. 根据状态码 来判断token是否过期
    final refreshToken = tokenManager.getRefreshToken();
    // 注意点: 自己创建一个dio实例 -> 为了避免使用之前的dio实例发请求 (携带的token -> 过期的)
    Dio dio = Dio();
    try {
      final res = await dio.post(
          '${GlobalVariable.BASE_URL}${HTTP_PATH.REFRESH_TOKEN}',
          options: Options(headers: {'Authorization': 'Bearer $refreshToken'}));
      if (res.statusCode == 200) {
        // 刷新token成功
        // 将获取到的最新的token和refreshToken 进行存储
        final token = res.data['data']['token'];
        final refreshToken = res.data['data']['refreshToken'];
        tokenManager.setToken(token, refreshToken: refreshToken);
        return true;
      } else {
        // 刷新token失败
        return false;
      }
    } catch (e) {
      return false;
    }
  }

  // 4.添加请求和响应拦截器
  _registerInterceptors() {
    _dio.interceptors.add(
      InterceptorsWrapper(
        // 请求拦截
        onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
          // 如果你想完成请求并返回一些自定义数据，你可以使用 `handler.resolve(response)`。
          // 如果你想终止请求并触发一个错误，你可以使用 `handler.reject(error)`。
          final token = tokenManager.getToken();
          if (token.isNotEmpty) {
            // 在请求头中携带token
            options.headers['Authorization'] = 'Bearer $token';
          }
          return handler.next(options);
        },
        // 响应拦截
        onResponse: (Response response, ResponseInterceptorHandler handler) {
          // 如果你想终止请求并触发一个错误，你可以使用 `handler.reject(error)`。
          // 针对于http状态码结果 大于等于  200 小于 300 （请求成功）则返回数据
          if (response.statusCode! >= 200 && response.statusCode! < 300) {
            return handler.next(response);
          } else {
            // 4开头 (客户端异常 请求路径 错误 / 鉴权(token过期)) 5开头(服务器端异常)
            // 错误提示
            PrompAction.showError(response.statusMessage!);
            // 返回一个错误的Future / 鸿蒙 前端 是promise
            return handler.reject(DioException(
              requestOptions: response.requestOptions,
            ));
          }
        },
        // 异常处理
        onError: (DioException error, ErrorInterceptorHandler handler) async {
          // 如果你想完成请求并返回一些自定义数据，你可以使用 `handler.resolve(response)`。
          // 401 token失效的逻辑
          if (error.response?.statusCode == 401) {
            final refreshToken = tokenManager.getRefreshToken();
            if (refreshToken != null) {
              final bool isExpired = await _isRefreshsTokenExpired();
              if (isExpired) {
                // refreshToken有效 -> 存储新token 和 新refreshToken 成功了
                // 一旦refreshToken有效, 存储了最新的token和最新的refreshToken
                // 上述步骤, 从数据层面, 但是视图层面, 需要及时更新
                // 我的页面, 刚才是请求401 -> 拿着最新的token, 重新再次发送请求 -> 更新我的页面用户数据
                
                // 尝试发起之前失败的请求
                // error.requestOptions (上次请求的参数对象)
                // 再次发起请求, 拿成功的结果 dio.fetch(error.requestOptions)
                final res = await _dio.fetch(error.requestOptions);
                return handler.resolve(res);
              } else {
                tokenManager.removeToken();
                eventBus.fire(logoutEvent());
              }
            } else {
              // 刷新token存在时
              // 清除本地缓存
              // 跳转到登录页
              tokenManager.removeToken();
              // 非页面级别的组件中无法使用context上下文对象
              // Navigator.pushNamed(context, '/login');
              // eventbus 事件总线 -> vue中
              // 发布订阅模式 -> 发布者 / 订阅者
              // 鸿蒙中 emitter / 前端 eventBus
              // 1. 发布一个消息
              eventBus.fire(logoutEvent());
              // 2. 其他可以跳转的位置 进行订阅 -> 跳转
            }
          }

          return handler.next(error);
        },
      ),
    );
  }
}

final requestDio = RequestDio();
